﻿using System;
using System.Collections.Generic;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System.Windows.Forms;
using System.Drawing;
using System.Reflection;

namespace AutomationFramework
{
    /// <summary>
    /// Provider for the MeasurementItem in the MeasurementControl..
    /// </summary>
    public class ObjectProvider : FragmentProvider, IValueProvider, IToggleProvider, IInvokeProvider
    {
        #region Constants
        private const int ourProviderOptionUseComThreading = 0x20;
        private const int ourProviderDescriptionId = 30107; // from MSDN 
        #endregion

        #region Fields
        private Control myControl;
        private Object myObject;
        private ApiMapping myApiMapping;
        #endregion

        #region Constructors
        public ObjectProvider(Control control, IRawElementProviderFragmentRoot root, Object value, ApiMapping apiMapping)
            : base(root, root) // (Parent, FragmentRoot)
        {
            this.myControl = control;
            this.myObject = value;
            this.myApiMapping = apiMapping;

            // Populate static properties            
            // TODO: Name should be localized.
            // Next Line Commented since we have overridden NameProperty.
            //AddAutomationProperty(AutomationElementIdentifiers.NameProperty.Id, this.myItem.Id.ToString());

            AddAutomationProperty(AutomationElementIdentifiers.ClassNameProperty.Id, myApiMapping.ControlClassName);
            AddAutomationProperty(AutomationElementIdentifiers.ControlTypeProperty.Id, ControlType.Custom.Id);
            // TODO: LocalizedControlType should be localized.
            AddAutomationProperty(AutomationElementIdentifiers.LocalizedControlTypeProperty.Id, myApiMapping.ObjectLocalizedControlTypeProperty);
            AddAutomationProperty(ourProviderDescriptionId, myApiMapping.ObjectProviderDescriptionId);
            AddAutomationProperty(AutomationElementIdentifiers.HelpTextProperty.Id, myApiMapping.ObjectHelpTextProperty);

            // The WinForm name for this control makes a good Automation ID.
            //AddAutomationProperty(AutomationElementIdentifiers.AutomationIdProperty.Id, this.myItem.Id.ToString());
            AddAutomationProperty(AutomationElementIdentifiers.IsKeyboardFocusableProperty.Id, false);
            AddAutomationProperty(AutomationElementIdentifiers.IsControlElementProperty.Id, true);
            AddAutomationProperty(AutomationElementIdentifiers.IsContentElementProperty.Id, false);
            AddAutomationProperty(AutomationElementIdentifiers.IsEnabledProperty.Id, true);

            // Set Implemented Patterns
            if (myApiMapping.ObjectIValueProviderAvailable)
            {
                AddAutomationProperty(AutomationElementIdentifiers.IsValuePatternAvailableProperty.Id, true);
            }
            if (myApiMapping.ObjectIToggleProviderAvailable)
            {
                AddAutomationProperty(AutomationElementIdentifiers.IsTogglePatternAvailableProperty.Id, true);
            }
            if (myApiMapping.ObjectIInvokeProviderAvailable)
            {
                AddAutomationProperty(AutomationElementIdentifiers.IsInvokePatternAvailableProperty.Id, true);
            }
        }
        #endregion

        #region Overridden Methods (Override base default behavior)
        public override ProviderOptions ProviderOptions
        {
            get
            {
                return (ProviderOptions)((int)ProviderOptions.ServerSideProvider | ourProviderOptionUseComThreading);
            }
        }

        public override object GetPatternProvider(int patternId)
        {
            // Return the patterns we have implemented.
            if (myApiMapping.ObjectIValueProviderAvailable && patternId == ValuePatternIdentifiers.Pattern.Id)
            {
                return this;
            }
            if (myApiMapping.ObjectIToggleProviderAvailable && patternId == TogglePatternIdentifiers.Pattern.Id)
            {
                return this;
            }
            if (myApiMapping.ObjectIInvokeProviderAvailable && patternId == InvokePatternIdentifiers.Pattern.Id)
            {
                return this;
            }
            return base.GetPatternProvider(patternId);
        }

        /// <summary>
        /// Create a runtime ID.  Since there is only one fragment per item,
        /// the item turned into an integer is a unique identifier that we
        /// can use as the runtime ID.
        /// </summary>
        /// <returns></returns>
        public override int[] GetRuntimeId()
        {
            int[] runtimeId = new int[2];
            runtimeId[0] = AutomationInteropProvider.AppendRuntimeId;
            // Since we dont have any int id with MeasurementItem, set its Y Location. :)
            runtimeId[1] = this.myObject.GetHashCode();
            return runtimeId;
        }

        /// <summary>
        /// Get the Name of the Element.
        /// </summary>
        /// <returns></returns>
        protected override string GetName()
        {
            return this.myObject.GetType().GetProperty(myApiMapping.ObjectName).GetValue(myObject, null).ToString();
        }

        /// <summary>
        /// Get the bounding rect by consulting the control.
        /// </summary>
        public override System.Windows.Rect BoundingRectangle
        {
            get
            {
                // Bounding rects must be in screen coordinates
                object obj = (myObject.GetType().GetProperty(myApiMapping.ObjectBoundingRectangle).GetValue(myObject, null));
                Rectangle rect = (Rectangle)obj;
                System.Drawing.Rectangle screenRect = this.myControl.RectangleToScreen(rect);
                return new System.Windows.Rect(screenRect.Left, screenRect.Top, screenRect.Width, screenRect.Height);
            }
        }

        /// <summary>
        /// Return the fragment for the next value
        /// </summary>
        /// <returns></returns>
        protected override IRawElementProviderFragment GetNextSibling()
        {
            Object propertyValue = (myControl.GetType().GetProperty(myApiMapping.ObjectItems).GetValue(myControl, null));
            if (propertyValue != null && propertyValue.GetType().IsArray)
            {
                Array array = propertyValue as Array;

                int index = Array.IndexOf(array, myObject);
                if (index < array.Length - 1)
                {
                    index++;
                    object item = (object)array.GetValue(index);

                    return new ObjectProvider(this.myControl, this.fragmentRoot, item, myApiMapping);
                }
            }
            return null;
        }

        /// <summary>
        /// Return the fragment for the previous value
        /// </summary>
        /// <returns></returns>
        protected override IRawElementProviderFragment GetPreviousSibling()
        {
            Object propertyValue = (myControl.GetType().GetProperty(myApiMapping.ObjectItems).GetValue(myControl, null));
            if (propertyValue != null && propertyValue.GetType().IsArray)
            {
                Array array = propertyValue as Array;

                int index = Array.IndexOf(array, myObject);
                if (index > 0)
                {
                    index--;
                    object item = (object)array.GetValue(index);

                    return new ObjectProvider(this.myControl, this.fragmentRoot, item, myApiMapping);
                }
            }
            return null;
        }
        #endregion

        #region IValueProvider Members

        public bool IsReadOnly
        {
            get { return myApiMapping.ObjectIValueProviderIsReadOnly; }
        }

        public void SetValue(string value)
        {
            if (myApiMapping.ObjectIValueProviderAvailable)
            {
                myObject.GetType().GetProperty(myApiMapping.ObjectIValueProviderValue).SetValue(myObject, value, null);
            }
        }

        public string Value
        {
            get
            {
                string name = null; ;
                if (myApiMapping.ObjectIValueProviderAvailable)
                {
                    name = myObject.GetType().GetProperty(myApiMapping.ObjectIValueProviderValue).GetValue(myObject, null).ToString();
                }
                return name;
            }
        }

        #endregion

        #region IToggleProvider Members
        
        public void Toggle()
        {
            if (myApiMapping.ObjectIToggleProviderAvailable)
            {
                bool toogleState = ToggleState == ToggleState.On ? false : true;
                myObject.GetType().GetProperty(myApiMapping.ObjectIToggleProviderToggle).SetValue(myObject, toogleState, null);
            }
        }

        public ToggleState ToggleState
        {
            get
            {
                if (myApiMapping.ObjectIToggleProviderAvailable)
                {
                    bool state = Convert.ToBoolean(myObject.GetType().GetProperty(myApiMapping.ObjectIToggleProviderToggleState).GetValue(myObject, null));
                    if (state)
                    {
                        return ToggleState.On;
                    }
                }
                return ToggleState.Off;
            }
        }

        #endregion

        #region IInvokeProvider Members

        public void Invoke()
        {
            if (myApiMapping.ObjectIInvokeProviderAvailable)
            {
                // To Simulate Left Mouse Click on User Control.
                MouseEventArgs e = new MouseEventArgs(MouseButtons.Left, 1, 70, 20, 0);
                MethodInfo methodToInvoke = myObject.GetType().GetMethod(myApiMapping.ObjectIInvokeProviderInvoke,
                    BindingFlags.NonPublic | BindingFlags.Instance);
                methodToInvoke.Invoke(myControl, new object[] { e });
            }
        }

        #endregion
    }
}
